﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq.Expressions;
using System.Runtime.Serialization;
using System.Text;
using System.Xml.Linq;

namespace QDCommon.Xml
{
    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = false, AllowMultiple = false)]
    public class IgnoreAttribute : Attribute
    {

    }
    public class XmlHelper
    {
        #region 对象转xml
        public static Dictionary<string, object> objToXmlDic = new Dictionary<string, object>();

        public static XElement ObjectToXml<T>(T obj)
        {
            DataContractSerializer dcs = new DataContractSerializer(typeof(T));
            string xmlStr = string.Empty;
            using (MemoryStream ms = new MemoryStream())
            {
                try
                {
                    dcs.WriteObject(ms, obj);
                    xmlStr = Encoding.UTF8.GetString(ms.ToArray());
                    ms.Close();
                }
                catch
                {
                    throw;
                }
            }
           return  XElement.Parse(xmlStr);
        }
        public static XElement ConvertObjToXml<T>(T obj,string rootName)
        {
            return CreateObjToXml<T>(rootName)(obj);
        }
        public static Func<T, XElement> CreateObjToXml<T>(string rootName)
        {
            string key = typeof(T).FullName+rootName;

            if (!objToXmlDic.ContainsKey(key))
            {

                List<Expression> expressions = new List<Expression>();
                var xelType = typeof(XElement);
                var tType = typeof(T);
                var constructor1 = xelType.GetConstructor(new Type[] { typeof(XName) });
                var constructor2 = xelType.GetConstructor(new Type[] { typeof(XName), typeof(object) });
                var nameConstructor = typeof(XName).GetConstructor(new Type[] { typeof(string) });
                var add = xelType.GetMethod("Add", new Type[] { typeof(object) });
                var returnLabel = Expression.Label(xelType);
                var result = Expression.Variable(xelType, "result");
                var para = Expression.Parameter(tType, "obj");
                BinaryExpression setResult = Expression.Assign(result, Expression.New(constructor1, new Expression[] { Expression.Constant(XName.Get(rootName)) }));
                expressions.Add(setResult);
                foreach (var item in tType.GetProperties())
                {
                    var attribute = item.GetCustomAttributes(typeof(IgnoreAttribute), false);
                    if (attribute == null||attribute.Length == 0)
                    {
                        var value = Expression.Property(para, item);
                        XName name = XName.Get(item.Name);
                        if (item.PropertyType.IsClass)
                        {
                            var newElement = Expression.New(constructor2, new Expression[] { Expression.Constant(name), value });
                            var ifNotEmpty = Expression.IfThen(Expression.NotEqual(value, Expression.Constant(null)), Expression.Call(result, add, new Expression[] { newElement }));
                            //var setVlue = Expression.Call(result, add, new Expression[] { newElement });
                            //expressions.Add(setVlue);
                            expressions.Add(ifNotEmpty);
                        }
                        else
                        {
                            var newElement = Expression.New(constructor2, new Expression[] { Expression.Constant(name), Expression.Call(value, "ToString", null) });
                            var setValue = Expression.Call(result, add, new Expression[] { newElement });
                            expressions.Add(setValue);
                        }
                    }

                }

                GotoExpression ge = Expression.Return(returnLabel, result, xelType);
                var lbl = Expression.Label(returnLabel, Expression.New(constructor1, new Expression[] { Expression.Constant(XName.Get(rootName)) }));
                expressions.Add(ge);
                expressions.Add(lbl);
                BlockExpression blocks = Expression.Block(new ParameterExpression[] { result }, expressions);
                Expression<Func<T, XElement>> func = Expression.Lambda<Func<T, XElement>>(blocks, para);
                objToXmlDic[key] = func.Compile(); ;
            }
            return objToXmlDic[key] as Func<T, XElement>;
        }
        #endregion


        #region xml转对象
        public static Dictionary<string, object> toObjDic = new Dictionary<string, object>();
        public static T ConvertXmltToObject<T>(XElement xe)
        {
            return ConvertXML<T>()(xe);
        }
        /// <summary>
        /// xelement转换成制定的类
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public static Func<XElement, T> ConvertXML<T>()
        {

            string key = typeof(T).FullName;

            if (!toObjDic.ContainsKey(key))
            {

                List<Expression> expressions = new List<Expression>();
                var xelType = typeof(XElement);
                var convertMethod = typeof(System.Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) });

                ParameterExpression parameterExpression = Expression.Parameter(xelType, "xel");
                var returnLabel = Expression.Label(typeof(T));
                var result = Expression.Variable(typeof(T), "result");
                var element = Expression.Variable(typeof(XElement), "element");
                var elementMethod = xelType.GetMethod("Element");
                BinaryExpression setResult = Expression.Assign(result, Expression.New(typeof(T)));

                expressions.Add(setResult);
                foreach (var item in typeof(T).GetProperties())
                {
                    var attribute = item.GetCustomAttributes(typeof(IgnoreAttribute), false);
                    if (attribute == null || attribute.Length == 0)
                    {

                        XName name = XName.Get(item.Name);
                        var getElement = Expression.Call(parameterExpression, elementMethod, Expression.Constant(name));
                        var setElement = Expression.Assign(element, getElement);
                        var property = Expression.Property(result, item);

                        var notEqual = Expression.NotEqual(element, Expression.Constant(null));

                        var value = Expression.Property(element, "Value");
                        var notEmpty = Expression.NotEqual(value, Expression.Constant(string.Empty));
                        var toString = Expression.Call(value, "ToString", null);
                        //
                        var convert = Expression.Call(typeof(System.Convert), "ChangeType", new Type[] { }, toString, Expression.Constant(item.PropertyType));
                        var convert1 = Expression.Convert(convert, item.PropertyType);
                        var setPropertyValue = Expression.Assign(property, convert1);



                        var ifNotEmpty = Expression.IfThenElse(notEmpty, setPropertyValue, Expression.Empty());
                        var ifNotNull = Expression.IfThenElse(notEqual, ifNotEmpty, Expression.Empty());
                        expressions.Add(getElement);
                        expressions.Add(setElement);
                        expressions.Add(ifNotNull);
                    }
                }
                GotoExpression ge = Expression.Return(returnLabel, result, typeof(T));
                var lbl = Expression.Label(returnLabel, Expression.New(typeof(T)));
                expressions.Add(ge);
                expressions.Add(lbl);
                BlockExpression blocks = Expression.Block(new ParameterExpression[] { result, element }, expressions);
                Expression<Func<XElement, T>> func = Expression.Lambda<Func<XElement, T>>(blocks, parameterExpression);

                toObjDic[key] = func.Compile(); ;
            }
            return toObjDic[key] as Func<XElement, T>;
        }
        #endregion
    }
}
